import React, { useEffect, useMemo, useRef, useState, useCallback, useImperativeHandle } from 'react'
import * as THREE from 'three'
import { Canvas, useFrame } from '@react-three/fiber'
import { OrbitControls, Stars, Sparkles, Sky, Html } from '@react-three/drei'
import { EffectComposer, Bloom, Vignette, DepthOfField } from '@react-three/postprocessing'

const clamp = (v, a, b) => Math.min(b, Math.max(a, v))
const rand = (a, b) => a + Math.random() * (b - a)
const isMobile = () => typeof navigator !== 'undefined' && /Mobi|Android|iPhone|iPad|iPod/i.test(navigator.userAgent)

// ---- Texture helpers (round particle & soft smoke) ----
function makeCircleTex(size = 64, alpha = 1) {
  const canvas = document.createElement('canvas')
  canvas.width = canvas.height = size
  const ctx = canvas.getContext('2d')
  const g = ctx.createRadialGradient(size/2, size/2, 0, size/2, size/2, size/2)
  g.addColorStop(0, `rgba(255,255,255,${alpha})`)
  g.addColorStop(0.5, `rgba(255,255,255,${alpha*0.7})`)
  g.addColorStop(1, 'rgba(255,255,255,0)')
  ctx.fillStyle = g
  ctx.fillRect(0,0,size,size)
  const tex = new THREE.CanvasTexture(canvas)
  tex.colorSpace = THREE.SRGBColorSpace
  tex.MagFilter = THREE.LinearFilter
  tex.minFilter = THREE.LinearMipMapLinearFilter
  tex.anisotropy = 8
  return tex
}

const circleTex = makeCircleTex(128, 1.0)
const smokeTex = makeCircleTex(256, 0.6)

// ---- Volcano mesh ----
function Volcano({ radius = 1.3, height = 3.2 }) {
  const geom = useMemo(() => {
    const g = new THREE.CylinderGeometry(0.25, radius, height, 128, 48, false)
    const pos = g.attributes.position
    const v = new THREE.Vector3()
    for (let i = 0; i < pos.count; i++) {
      v.fromBufferAttribute(pos, i)
      const r = Math.hypot(v.x, v.z)
      const hNorm = (v.y + height/2) / height
      const rough = (Math.sin(r * 3.1 + hNorm * 4.2) + Math.cos(r * 5.7 - hNorm * 2.7)) * 0.06 * (0.6 + 0.8 * (1 - hNorm))
      v.x *= 1 + rough; v.z *= 1 + rough
      if (hNorm > 0.8) { const t = (hNorm - 0.8) / 0.2; v.x *= 1 - 0.35 * t; v.z *= 1 - 0.35 * t }
      pos.setXYZ(i, v.x, v.y, v.z)
    }
    pos.needsUpdate = true; g.computeVertexNormals()
    return g
  }, [radius, height])
  return (
    <mesh geometry={geom} castShadow receiveShadow position={[0, height / 2 - 0.15, 0]}>
      <meshStandardMaterial color={new THREE.Color(0x3a2a22)} roughness={0.95} metalness={0.05} />
    </mesh>
  )
}

function Ground({ color = 0x161616 }) {
  return (
    <mesh rotation={[-Math.PI / 2, 0, 0]} receiveShadow>
      <planeGeometry args={[70, 70, 1, 1]} />
      <meshStandardMaterial color={new THREE.Color(color)} />
    </mesh>
  )
}

// ---- Eruption particles (round + additive + soft) ----
function Eruption({ power, wind, count, type, baseHue }) {
  const points = useRef()
  const positions = useMemo(() => new Float32Array(count * 3), [count])
  const colors = useMemo(() => new Float32Array(count * 3), [count])
  const velocities = useMemo(() => new Float32Array(count * 3), [count])
  const life = useMemo(() => new Float32Array(count), [count])

  const resetOne = (i, p, w) => {
    const idx = i * 3
    positions[idx + 0] = rand(-0.08, 0.08)
    positions[idx + 1] = 1.72
    positions[idx + 2] = rand(-0.08, 0.08)
    const angle = rand(0, Math.PI * 2)

    // eruption types: 1=Strombolian, 2=Vulcanian, 3=Plinian
    const base = type === 1 ? 2.2 : type === 2 ? 3.0 : 4.2
    const spread = type === 1 ? 0.18 : type === 2 ? 0.28 : 0.35
    const speed = rand(base, base + 1.5)

    velocities[idx + 0] = Math.cos(angle) * spread * p + w * 0.9
    velocities[idx + 1] = speed * p
    velocities[idx + 2] = Math.sin(angle) * spread * p
    life[i] = rand(1.1, 2.7)

    // choose colour based on a provided baseHue or default warm hues
    let h;
    if (typeof baseHue === 'number') {
      h = baseHue + rand(-0.015, 0.015)
      h = ((h % 1) + 1) % 1
    } else {
      h = rand(0.02, 0.07)
    }
    const c = new THREE.Color().setHSL(h, 1, rand(0.45, 0.65))
    colors[idx + 0] = c.r; colors[idx + 1] = c.g; colors[idx + 2] = c.b
  }

  useEffect(() => {
    for (let i = 0; i < count; i++) resetOne(i, power, wind)
    if (points.current) {
      points.current.geometry.attributes.position.needsUpdate = true
      points.current.geometry.attributes.color.needsUpdate = true
    }
  }, [count]) // eslint-disable-line

  useFrame((_, dt) => {
    const p = points.current; if (!p) return
    const pos = positions; const col = colors; const vel = velocities
    for (let i = 0; i < count; i++) {
      const idx = i * 3
      life[i] -= dt
      if (life[i] <= 0 || pos[idx + 1] < 0.2) {
        resetOne(i, power, wind)
      } else {
        vel[idx + 1] -= 4.7 * dt
        vel[idx + 0] += wind * 0.32 * dt
        pos[idx + 0] += vel[idx + 0] * dt
        pos[idx + 1] += vel[idx + 1] * dt
        pos[idx + 2] += vel[idx + 2] * dt
        // cool color
        col[idx + 0] = Math.max(0.08, col[idx + 0] - 0.33 * dt)
        col[idx + 1] = Math.max(0.05, col[idx + 1] - 0.28 * dt)
      }
    }
    p.geometry.attributes.position.needsUpdate = true
    p.geometry.attributes.color.needsUpdate = true
  })

  return (
    <points ref={points}>
      <bufferGeometry>
        <bufferAttribute attach="attributes-position" count={positions.length / 3} array={positions} itemSize={3} />
        <bufferAttribute attach="attributes-color" count={colors.length / 3} array={colors} itemSize={3} />
      </bufferGeometry>
      <pointsMaterial
        size={0.12}
        sizeAttenuation
        map={circleTex}
        transparent
        depthWrite={false}
        vertexColors
        blending={THREE.AdditiveBlending}
      />
    </points>
  )
}

// ---- Smoke (larger soft particles + low opacity) ----
function Smoke({ wind, count }) {
  const points = useRef()
  const positions = useMemo(() => new Float32Array(count * 3), [count])
  const velocities = useMemo(() => new Float32Array(count * 3), [count])

  const resetOne = (i) => {
    const idx = i * 3
    positions[idx + 0] = rand(-0.2, 0.2)
    positions[idx + 1] = rand(1.8, 2.0)
    positions[idx + 2] = rand(-0.2, 0.2)
    velocities[idx + 0] = rand(-0.04, 0.04)
    velocities[idx + 1] = rand(0.22, 0.5)
    velocities[idx + 2] = rand(-0.04, 0.04)
  }

  useEffect(() => {
    for (let i = 0; i < count; i++) resetOne(i)
    if (points.current) points.current.geometry.attributes.position.needsUpdate = true
  }, [count]) // eslint-disable-line

  useFrame((_, dt) => {
    const p = points.current; if (!p) return
    const pos = positions; const vel = velocities
    for (let i = 0; i < count; i++) {
      const idx = i * 3
      pos[idx + 0] += (vel[idx + 0] + wind * 0.22) * dt
      pos[idx + 1] += vel[idx + 1] * dt
      pos[idx + 2] += vel[idx + 2] * dt
      if (pos[idx + 1] > 6 || Math.hypot(pos[idx + 0], pos[idx + 2]) > 3.5) resetOne(i)
    }
    p.geometry.attributes.position.needsUpdate = true
  })

  return (
    <points ref={points}>
      <bufferGeometry>
        <bufferAttribute attach="attributes-position" count={positions.length / 3} array={positions} itemSize={3} />
      </bufferGeometry>
      <pointsMaterial
        size={0.55}
        sizeAttenuation
        map={smokeTex}
        transparent
        opacity={0.22}
        depthWrite={false}
        color={new THREE.Color(0xaaaaaa)}
      />
    </points>
  )
}

// ---- Pyroclastic flows (ground-hugging ash clouds) with optional glow ----
function PyroFlows({ strength, wind, count, color }) {
  const points = useRef()
  const positions = useMemo(() => new Float32Array(count * 3), [count])
  const velocities = useMemo(() => new Float32Array(count * 3), [count])
  const life = useMemo(() => new Float32Array(count), [count])

  const resetOne = useCallback((i) => {
    const idx = i * 3
    positions[idx + 0] = rand(-0.3, 0.3)
    positions[idx + 1] = 1.6 + rand(-0.1, 0.1)
    positions[idx + 2] = rand(-0.3, 0.3)
    const ang = rand(0, Math.PI * 2)
    const baseSpeed = 0.4 + strength * 0.6
    velocities[idx + 0] = Math.cos(ang) * baseSpeed
    velocities[idx + 1] = -rand(0.3, 0.6) * (0.4 + strength * 0.4)
    velocities[idx + 2] = Math.sin(ang) * baseSpeed
    life[i] = rand(1.0, 2.4)
  }, [strength])

  useEffect(() => {
    for (let i = 0; i < count; i++) resetOne(i)
    if (points.current) points.current.geometry.attributes.position.needsUpdate = true
  }, [count, resetOne])

  useFrame((_, dt) => {
    const p = points.current
    if (!p) return
    const pos = positions
    const vel = velocities
    for (let i = 0; i < count; i++) {
      const idx = i * 3
      life[i] -= dt
      if (life[i] <= 0 || pos[idx + 1] < 0.1 || Math.hypot(pos[idx + 0], pos[idx + 2]) > 6) {
        resetOne(i)
      } else {
        vel[idx + 1] -= 0.5 * dt
        vel[idx + 0] += wind * 0.3 * dt
        pos[idx + 0] += vel[idx + 0] * dt
        pos[idx + 1] += vel[idx + 1] * dt
        pos[idx + 2] += vel[idx + 2] * dt
      }
    }
    p.geometry.attributes.position.needsUpdate = true
  })

  const ashColor = useMemo(() => {
    const c = new THREE.Color(color || 0xff4400)
    return c.multiplyScalar(0.4)
  }, [color])

  return (
    <points ref={points}>
      <bufferGeometry>
        <bufferAttribute attach="attributes-position" count={positions.length / 3} array={positions} itemSize={3} />
      </bufferGeometry>
      <pointsMaterial
        size={0.28}
        sizeAttenuation
        map={smokeTex}
        transparent
        opacity={0.38}
        depthWrite={false}
        color={ashColor}
      />
    </points>
  )
}

// ---- Lightning effect ----
// Generates temporary lightning bolts when triggered. Use ref to call trigger() method.
const Lightning = React.forwardRef((props, ref) => {
  const [bolts, setBolts] = useState([])

  // function to create a new lightning bolt
  const trigger = useCallback(() => {
    // create random polyline from sky to crater
    const segments = Math.floor(rand(3, 6))
    const start = new THREE.Vector3(rand(-1, 1) * 2, 6 + rand(0, 2), rand(-1, 1) * 2)
    const end = new THREE.Vector3(0, 1.7, 0)
    const points = []
    points.push(start.clone())
    for (let i = 1; i < segments; i++) {
      const t = i / segments
      const interp = new THREE.Vector3().lerpVectors(start, end, t)
      // add some random jitter
      interp.x += rand(-0.3, 0.3)
      interp.z += rand(-0.3, 0.3)
      interp.y += rand(-0.5, 0.5)
      points.push(interp)
    }
    points.push(end.clone())
    const arr = new Float32Array(points.length * 3)
    for (let i = 0; i < points.length; i++) {
      arr[i * 3] = points[i].x
      arr[i * 3 + 1] = points[i].y
      arr[i * 3 + 2] = points[i].z
    }
    // each bolt has a short life (0.3s)
    setBolts(b => [...b, { positions: arr, life: 0.3 }])
  }, [])

  // expose trigger method via ref
  useImperativeHandle(ref, () => ({ trigger }), [trigger])

  // update life each frame
  useFrame((_, dt) => {
    setBolts(prev => prev.map(b => ({ ...b, life: b.life - dt })).filter(b => b.life > 0))
  })

  return (
    <>
      {bolts.map((bolt, idx) => (
        <line key={idx}
          geometry={(() => {
            const g = new THREE.BufferGeometry()
            g.setAttribute('position', new THREE.BufferAttribute(bolt.positions, 3))
            return g
          })()}
        >
          <lineBasicMaterial
            attach="material"
            color={new THREE.Color(1, 1, 0.8)}
            transparent
            opacity={bolt.life / 0.3}
          />
        </line>
      ))}
    </>
  )
})

function LavaCore({ strength, color = '#ff5a00' }) {
  const mesh = useRef()
  // compute emissive and base colours from provided colour
  const emissiveColor = useMemo(() => new THREE.Color(color), [color])
  const baseColor = useMemo(() => {
    const c = new THREE.Color(color)
    return c.clone().multiplyScalar(0.2)
  }, [color])
  useFrame(({ clock }) => {
    if (!mesh.current) return
    const t = clock.getElapsedTime()
    mesh.current.material.emissiveIntensity = 1 + strength * 1.2
    const s = 1 + strength * 0.18
    mesh.current.scale.setScalar(s)
    mesh.current.position.y = 1.72 + Math.sin(t * 5) * 0.02
  })
  return (
    <mesh ref={mesh} position={[0, 1.72, 0]}>
      <sphereGeometry args={[0.2, 32, 32]} />
      <meshStandardMaterial emissive={emissiveColor} color={baseColor} />
    </mesh>
  )
}

function Overlay({
  panelOpen, setPanelOpen,
  power, setPower,
  wind, setWind,
  safety, setSafety,
  edu, setEdu,
  type, setType,
  lavaColor, setLavaColor,
  pyroStrength, setPyroStrength,
  volcanoShape, setVolcanoShape,
  sceneType, setSceneType,
  onBurst,
  onTriggerLightning
}) {
  // collapsed view: show minimal button to open panel
  if (!panelOpen) {
    return (
      <div className="overlay" style={{ width:'fit-content', padding:'6px 10px', cursor:'pointer', right:'auto' }} onClick={() => setPanelOpen(true)}>
        🎛️ 展开控制面板
      </div>
    )
  }
  return (
    <div className="overlay">
      <div style={{ display:'flex', justifyContent:'space-between', alignItems:'center' }}>
        <div className="title">🌋 火山爆发互动 · Visual Plus</div>
        <button onClick={() => setPanelOpen(false)} className="btn" style={{ padding:'4px 8px', fontSize:'12px' }}>收起</button>
      </div>
      <div className="desc">按住鼠标或长按屏幕蓄力，松手强力喷发；W/S/A/D 改变风向；1/2/3 切换喷发类型。点击⚡触发闪电。</div>
      <div className="row">
        <label>喷发强度：<b>{power.toFixed(2)}</b></label>
        <input type="range" min={0.2} max={2.5} step={0.01} value={power} onChange={e=> setPower(parseFloat(e.target.value))} />
      </div>
      <div className="row">
        <label>风向强度（+X）：<b>{wind.toFixed(2)}</b></label>
        <input type="range" min={-1} max={1} step={0.01} value={wind} onChange={e=> setWind(parseFloat(e.target.value))} />
      </div>
      <div className="row">
        <label>岩浆颜色：</label>
        <input type="color" value={lavaColor} onChange={e => setLavaColor(e.target.value)} style={{ marginLeft:6, width:50, height:24, border:'none', background:'transparent' }} />
      </div>
      <div className="row">
        <label>碎屑流强度：<b>{pyroStrength.toFixed(2)}</b></label>
        <input type="range" min={0} max={2} step={0.01} value={pyroStrength} onChange={e => setPyroStrength(parseFloat(e.target.value))} />
      </div>
      <div className="row">
        <label>火山形状：</label>
        <span style={{ marginLeft:6 }}>
          <label><input type="radio" name="vshape" checked={volcanoShape==='composite'} onChange={()=>setVolcanoShape('composite')} /> 复合火山</label>
          <label style={{marginLeft:12}}><input type="radio" name="vshape" checked={volcanoShape==='shield'} onChange={()=>setVolcanoShape('shield')} /> 盾状火山</label>
          <label style={{marginLeft:12}}><input type="radio" name="vshape" checked={volcanoShape==='cone'} onChange={()=>setVolcanoShape('cone')} /> 锥状火山</label>
        </span>
      </div>
      <div className="row">
        <label>场景类型：</label>
        <span style={{ marginLeft:6 }}>
          <label><input type="radio" name="scene" checked={sceneType==='mountain'} onChange={()=>setSceneType('mountain')} /> 山地</label>
          <label style={{marginLeft:12}}><input type="radio" name="scene" checked={sceneType==='island'} onChange={()=>setSceneType('island')} /> 海岛</label>
          <label style={{marginLeft:12}}><input type="radio" name="scene" checked={sceneType==='ice'} onChange={()=>setSceneType('ice')} /> 冰岛</label>
        </span>
      </div>
      <div className="grid">
        <button onClick={onBurst} className="btn">💥 瞬时喷发</button>
        <button onClick={()=>{ setPower(1); setWind(0); setPyroStrength(0); }} className="btn">🔄 重置</button>
        <button onClick={onTriggerLightning} className="btn">⚡ 闪电攻击</button>
        <label className="check"><input type="checkbox" checked={safety} onChange={e=> setSafety(e.target.checked)} /> 安全模式</label>
        <label className="check"><input type="checkbox" checked={edu} onChange={e=> setEdu(e.target.checked)} /> 教育模式</label>
      </div>
      <div className="row">
        <label>喷发类型：
          <span style={{marginLeft:6}}>
            <label><input type="radio" name="etype" checked={type===1} onChange={()=>setType(1)} /> 1 斯特龙博利式</label>
            <label style={{marginLeft:12}}><input type="radio" name="etype" checked={type===2} onChange={()=>setType(2)} /> 2 伏尔加诺式</label>
            <label style={{marginLeft:12}}><input type="radio" name="etype" checked={type===3} onChange={()=>setType(3)} /> 3 普林尼式</label>
          </span>
        </label>
      </div>
    </div>
  )
}

function EduHUD({ show, power, wind, type }) {
  if (!show) return null
  const tempC = Math.round(clamp(700 + power * 500, 700, 1300))
  const typeName = type === 1 ? 'Strombolian（斯特龙博利式）' : type === 2 ? 'Vulcanian（伏尔加诺式）' : 'Plinian（普林尼式）'
  return (
    <div className="hud">
      <div className="card">
        <div style={{ fontSize:16, fontWeight:700 }}>📚 教育模式</div>
        <div className="grid2" style={{ marginTop:8 }}>
          <div className="card" style={{ background:'rgba(255,255,255,.05)' }}>
            <div className="muted2">估算岩浆温度</div>
            <div style={{ fontSize:20, fontWeight:800, marginTop:6 }}>{tempC}°C</div>
          </div>
          <div className="card" style={{ background:'rgba(255,255,255,.05)' }}>
            <div className="muted2">喷发类型</div>
            <div style={{ fontSize:14, fontWeight:800, marginTop:6 }}>{typeName}</div>
          </div>
          <div className="card" style={{ background:'rgba(255,255,255,.05)', gridColumn:'1 / -1', display:'flex', alignItems:'center', justifyContent:'space-between' }}>
            <span>风向（+X 为东）</span>
            <span style={{ display:'inline-flex', alignItems:'center', gap:8 }}>
              <span className="muted2" style={{ fontSize:12 }}>{wind.toFixed(2)}</span>
              <span style={{ display:'inline-block', width:32, height:32, transform:`rotate(${wind>=0?0:180}deg)` }}>
                <svg viewBox="0 0 24 24" fill="currentColor" width="32" height="32"><path d="M12 2l6 8h-4v12h-4V10H6z"/></svg>
              </span>
            </span>
          </div>
        </div>
      </div>
    </div>
  )
}

export default function App() {
  // core eruption parameters
  const [power, setPower] = useState(1)
  const [wind, setWind] = useState(0)
  const [bursting, setBursting] = useState(0)
  const [safety, setSafety] = useState(false)
  const [edu, setEdu] = useState(false)
  const [type, setType] = useState(2) // default Vulcanian

  // visual customisation
  const [panelOpen, setPanelOpen] = useState(true)
  const [lavaColor, setLavaColor] = useState('#ff5a00')
  const [pyroStrength, setPyroStrength] = useState(0)
  const [volcanoShape, setVolcanoShape] = useState('composite') // composite, shield, cone
  const [sceneType, setSceneType] = useState('mountain') // mountain, island, ice

  // Optimized particle counts for better performance
  const [eruptionCount, setEruptionCount] = useState(isMobile() ? 900 : 1600)
  const [smokeCount, setSmokeCount] = useState(isMobile() ? 300 : 500)
  const [starsCount, setStarsCount] = useState(isMobile() ? 600 : 1000)
  const [particlesQuality, setParticlesQuality] = useState(isMobile() ? 'low' : 'high')
  const [postFX, setPostFX] = useState(true)

  // charge to burst
  const charging = useRef(false)
  const charge = useRef(0)

  // audio context and sound functions
  const audioCtx = useRef(null)
  const playEruptionSound = useCallback((intensity = 1) => {
    if (typeof window === 'undefined') return
    if (!audioCtx.current) audioCtx.current = new (window.AudioContext || window.webkitAudioContext)()
    const ctx = audioCtx.current
    if (ctx.state === 'suspended' && ctx.resume) ctx.resume()
    const duration = 1.4
    const buffer = ctx.createBuffer(1, ctx.sampleRate * duration, ctx.sampleRate)
    const data = buffer.getChannelData(0)
    for (let i = 0; i < data.length; i++) {
      const decay = Math.pow(1 - i / data.length, 2.5)
      data[i] = (Math.random() * 2 - 1) * decay * 0.6 * (0.4 + intensity * 0.6)
    }
    const noise = ctx.createBufferSource()
    noise.buffer = buffer
    const filter = ctx.createBiquadFilter()
    filter.type = 'lowpass'
    filter.frequency.value = 400 + intensity * 700
    filter.Q.value = 0.7
    noise.connect(filter)
    filter.connect(ctx.destination)
    noise.start()
  }, [])

  // sharp crack sound (e.g. lightning or rock cracking)
  const playCrackSound = useCallback((intensity = 1) => {
    if (typeof window === 'undefined') return
    if (!audioCtx.current) audioCtx.current = new (window.AudioContext || window.webkitAudioContext)()
    const ctx = audioCtx.current
    if (ctx.state === 'suspended' && ctx.resume) ctx.resume()
    const osc = ctx.createOscillator()
    const gain = ctx.createGain()
    osc.type = 'triangle'
    const now = ctx.currentTime
    const amp = 0.4 * intensity
    gain.gain.setValueAtTime(amp, now)
    gain.gain.exponentialRampToValueAtTime(0.001, now + 0.3)
    osc.frequency.setValueAtTime(600 + intensity * 800, now)
    osc.frequency.exponentialRampToValueAtTime(200, now + 0.3)
    osc.connect(gain).connect(ctx.destination)
    osc.start()
    osc.stop(now + 0.3)
  }, [])

  // compute derived values
  const baseHue = useMemo(() => {
    const c = new THREE.Color(lavaColor)
    const hsl = { h: 0, s: 0, l: 0 }
    c.getHSL(hsl)
    return hsl.h
  }, [lavaColor])
  const pyroCount = useMemo(() => Math.max(0, Math.floor(pyroStrength * 800)), [pyroStrength])
  const pyroGlowCount = useMemo(() => Math.max(0, Math.floor(pyroStrength * 40)), [pyroStrength])
  const volcanoSettings = useMemo(() => {
    switch (volcanoShape) {
      case 'shield': return { radius: 2.4, height: 2.0 }
      case 'cone': return { radius: 1.1, height: 3.8 }
      default: return { radius: 1.3, height: 3.2 }
    }
  }, [volcanoShape])
  const groundColor = useMemo(() => {
    if (sceneType === 'island') return 0x052233
    if (sceneType === 'ice') return 0xbbccdd
    return 0x161616
  }, [sceneType])

  // lightning ref for triggering bolts
  const lightningRef = useRef()

  useEffect(() => {
    const onDown = () => { charging.current = true }
    const onUp = () => {
      if (charging.current) {
        const val = Math.min(2.2, 0.8 + charge.current * 0.8)
        setBursting(val)
        playEruptionSound(power + val)
        charging.current = false
        charge.current = 0
      }
    }
    window.addEventListener('pointerdown', onDown)
    window.addEventListener('pointerup', onUp)
    return () => { window.removeEventListener('pointerdown', onDown); window.removeEventListener('pointerup', onUp) }
  }, [playEruptionSound, power])

  // 移动 useFrame 钩子到 CameraShake 组件中
  const CameraShake = () => {
    useFrame((state, dt) => {
      if (charging.current) charge.current = Math.min(1.7, charge.current + dt)

      if (bursting > 0) {
        const t = Math.random() * 0.012 * bursting
        state.camera.position.x += (Math.random() - 0.5) * t
        state.camera.position.y += (Math.random() - 0.5) * t
        state.camera.position.z += (Math.random() - 0.5) * t
        setBursting(Math.max(0, bursting - dt * 1.6))
      }
    })
    return null
  }

  useEffect(() => {
    try {
      const mem = navigator.deviceMemory || 0
      if (isMobile() || (mem && mem < 4)) setSafety(true)
    } catch {}
  }, [])

  // randomly trigger lightning on large bursts
  const prevBurst = useRef(0)
  useEffect(() => {
    if (bursting > 0.8 && prevBurst.current <= 0.8) {
      if (Math.random() < 0.3) {
        if (lightningRef.current && lightningRef.current.trigger) lightningRef.current.trigger()
        playCrackSound(Math.max(1, bursting))
      }
    }
    prevBurst.current = bursting
  }, [bursting, playCrackSound])

  // Quality settings handler
  useEffect(() => {
    if (safety || particlesQuality === 'low') {
      setPostFX(false)
      setEruptionCount(isMobile() ? 600 : 900)
      setSmokeCount(isMobile() ? 200 : 300)
      setStarsCount(isMobile() ? 400 : 600)
    } else if (particlesQuality === 'medium') {
      setPostFX(true)
      setEruptionCount(isMobile() ? 1000 : 1300)
      setSmokeCount(isMobile() ? 300 : 400)
      setStarsCount(isMobile() ? 600 : 800)
    } else {
      setPostFX(true)
      setEruptionCount(isMobile() ? 1300 : 1800)
      setSmokeCount(isMobile() ? 400 : 600)
      setStarsCount(isMobile() ? 800 : 1200)
    }
  }, [safety, particlesQuality])

  // Add quality control button
  const toggleQuality = () => {
    setParticlesQuality(q => {
      if (q === 'low') return 'medium'
      if (q === 'medium') return 'high'
      return 'low'
    })
  }

  useEffect(() => {
    const onKey = (e) => {
      if (e.key === 'w' || e.key === 'W') setWind(w => clamp(w + 0.05, -1, 1))
      if (e.key === 's' || e.key === 'S') setWind(w => clamp(w - 0.05, -1, 1))
      if (e.key === 'a' || e.key === 'A') setWind(w => clamp(w - 0.03, -1, 1))
      if (e.key === 'd' || e.key === 'D') setWind(w => clamp(w + 0.03, -1, 1))
      if (e.key === '1') setType(1)
      if (e.key === '2') setType(2)
      if (e.key === '3') setType(3)
    }
    window.addEventListener('keydown', onKey)
    return () => window.removeEventListener('keydown', onKey)
  }, [])

  // trigger a standard burst with audio
  const burstNow = useCallback(() => {
    const intensity = 1.4
    setBursting(intensity)
    playEruptionSound(power + intensity)
  }, [power, playEruptionSound])

  // Check if device is mobile
  function isMobile() {
    if (typeof window === 'undefined') return false
    return /Mobi|Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(window.navigator.userAgent)
  }

  return (
    <div style={{ position:'relative', width:'100%', height:'100%' }}>
      <button 
        onClick={toggleQuality}
        style={{ position:'absolute', top:10, left:10, zIndex:100, padding:'4px 8px', backgroundColor:'rgba(0,0,0,0.5)', color:'white', border:'none', borderRadius:'4px', cursor:'pointer' }}
      >
        粒子质量: {particlesQuality === 'low' ? '低' : particlesQuality === 'medium' ? '中' : '高'}
      </button>
      <Canvas
        dpr={isMobile() ? 1 : [1, 2.5]}
        shadows
        camera={{ position: [6, 3.6, 6], fov: 55 }}
        gl={gl => {
          gl.toneMapping = THREE.ACESFilmicToneMapping
          gl.toneMappingExposure = 1.08
          gl.outputColorSpace = THREE.SRGBColorSpace
          // Performance optimizations
          gl.antialias = !isMobile()
        }}
        scene={{ background: new THREE.Color(0x06070a) }}
        onPointerDown={burstNow}
      >
        <fog attach="fog" args={[0x06070a, 15, 45]} />
        <Sky distance={450000} turbidity={8} rayleigh={3} mieCoefficient={0.006} mieDirectionalG={0.9} inclination={0.48} azimuth={0.35} />
        <Stars radius={100} depth={30} count={starsCount} factor={isMobile() ? 1.5 : 2.5} fade speed={isMobile() ? 0.7 : 1} />

        <ambientLight intensity={0.3} />
        <directionalLight castShadow position={[3, 6, 3]} intensity={1.25}>
          <orthographicCamera attach="shadow-camera" args={[-10,10,10,-10,0.1,30]} />
        </directionalLight>
        <pointLight position={[0, 2.3, 0]} intensity={6} color={new THREE.Color(lavaColor)} distance={9} decay={2} />

        <Ground color={groundColor} />
        <Volcano radius={volcanoSettings.radius} height={volcanoSettings.height} />
        <LavaCore strength={power + bursting * 0.3} color={lavaColor} />
        <Eruption power={power + bursting * 0.6} wind={wind} count={eruptionCount} type={type} baseHue={baseHue} />
        <Smoke wind={wind} count={smokeCount} />
        {/* Pyroclastic flows and glow */}
        {pyroCount > 0 && <PyroFlows strength={pyroStrength} wind={wind} count={pyroCount} color={lavaColor} />}
        {pyroGlowCount > 0 && <Sparkles position={[0,0.8,0]} count={pyroGlowCount} scale={[5,1.2,5]} size={3} speed={0.3} opacity={0.6} color={new THREE.Color(lavaColor)} />}
        {/* Sparkles at crater */}
        <Sparkles position={[0,1.6,0]} count={type===3?30:18} scale={[0.9,0.7,0.9]} size={5} speed={0.5} opacity={0.5} color={new THREE.Color(lavaColor)} />
        {/* Lightning bolts */}
        <Lightning ref={lightningRef} />

        <OrbitControls enablePan={false} maxPolarAngle={Math.PI * 0.495} minDistance={3} maxDistance={12} />
        <CameraShake />

        {postFX && (
          <EffectComposer>
            <Bloom intensity={isMobile() ? 0.8 : 1.1} luminanceThreshold={0.2} luminanceSmoothing={0.6} mipmapBlur />
            {!isMobile() && <DepthOfField focusDistance={0.015} focalLength={0.015} bokehScale={1.2} />}
            <Vignette darkness={0.7} eskil={false} />
          </EffectComposer>
        )}
      </Canvas>

      <Overlay
        panelOpen={panelOpen} setPanelOpen={setPanelOpen}
        power={power} setPower={setPower}
        wind={wind} setWind={setWind}
        safety={safety} setSafety={setSafety}
        edu={edu} setEdu={setEdu}
        type={type} setType={setType}
        lavaColor={lavaColor} setLavaColor={setLavaColor}
        pyroStrength={pyroStrength} setPyroStrength={setPyroStrength}
        volcanoShape={volcanoShape} setVolcanoShape={setVolcanoShape}
        sceneType={sceneType} setSceneType={setSceneType}
        onBurst={burstNow}
        onTriggerLightning={() => {
          if (lightningRef.current && lightningRef.current.trigger) lightningRef.current.trigger()
          playCrackSound(1)
          // special eruption when lightning hits
          setBursting(2.2)
          playEruptionSound(power + 2.2)
        }}
      />

      <EduHUD show={edu} power={power} wind={wind} type={type} />

      <div className="hint">拖拽旋转 · 滚轮/双指缩放 · 长按蓄力后松手</div>
    </div>
  )
}
